package com.zhongpengcheng.blog.service.impl;

import cn.hutool.core.lang.Snowflake;
import com.alibaba.fastjson.JSON;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.zhongpengcheng.blog.enums.FileTypeEnum;
import com.zhongpengcheng.blog.enums.ResultCodeEnum;
import com.zhongpengcheng.blog.exception.BizException;
import com.zhongpengcheng.blog.pojo.ro.FileUploadRO;
import com.zhongpengcheng.blog.properties.OssProperties;
import com.zhongpengcheng.blog.service.OssService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.PostConstruct;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;

/**
 * @author ZhongPengCheng
 * @version 1.0
 * @date 2021-12-04 14:36:00
 */
@Service
@Slf4j
public class OssServiceImpl implements OssService {

    @Autowired
    private OssProperties prop;
    /**
     * 唯一文件名生成最大尝试次数
     */
    private static final Integer MAX_TRY_COUNT = 5;

    @Autowired
    private Snowflake defaultIdGenerator;

    @Override
    public FileUploadRO save(String path, File file, FileTypeEnum fileType) {
        try {
            return save(path, FileCopyUtils.copyToByteArray(file), fileType);
        } catch (IOException e) {
            log.error("写入文件到OSS失败, path={}, fileType={}, error={}", path, fileType, e);
            throw new BizException(ResultCodeEnum.FILE_READ_FAILED);
        }
    }

    @Override
    public FileUploadRO save(String path, MultipartFile multipartFile, FileTypeEnum fileType) {
        try {
            return save(path, multipartFile.getBytes(), fileType);
        } catch (IOException e) {
            log.error("写入文件到OSS失败, path={}, fileType={}, error={}", path, fileType, e);
            throw new BizException(ResultCodeEnum.FILE_READ_FAILED);
        }
    }

    @Override
    public FileUploadRO save(String path, byte[] bytes, FileTypeEnum fileType) {

        try {
            OSSClient client = newClient();
            client.putObject(prop.getBucket(), path, new ByteArrayInputStream(bytes));
            client.shutdown();
            return FileUploadRO.of().setUrl(prop.getUrlPrefix() + path);
        } catch (Exception e) {
            log.error("写入文件到OSS失败, path={}, fileType={}, error={}", path, fileType, e);
            throw new BizException(ResultCodeEnum.OSS_UPDATE_FAILED);
        }
    }

    @Override
    public boolean exists(String path) {
        OSSClient client = newClient();
        try {
            return client.doesObjectExist(prop.getBucket(), path);
        } finally {
            client.shutdown();
        }
    }

    @Override
    public boolean notExists(String path) {
        return !exists(path);
    }

    @Override
    public String generateUniquePath(String filename, FileTypeEnum fileType) {
        int count = 0;
        String path;
        do {
            path = fileType.getOssPrefix() + defaultIdGenerator.nextIdStr() + "_" + filename;
            if (this.notExists(path)) {
                return path;
            }
            count++;
        } while (count < MAX_TRY_COUNT);
        throw new BizException(ResultCodeEnum.GEN_UNIQUE_PATH_FAILED);
    }

    @Override
    public boolean delete(String path) {
        if (notExists(path)) {
            return true;
        }
        OSSClient client = newClient();
        boolean success = false;
        try {
            client.deleteObject(prop.getBucket(), path);
            success = true;
        } catch (Exception e) {
            log.error("删除OSS文件异常, path={}, error={}", path, e);
        } finally {
            client.shutdown();
        }
        return success;
    }

    /**
     * 创建oss client实例
     *
     * @return client实例，使用完毕需关闭
     * @throws OSSException 创建client失败时抛出的异常
     */
    private OSSClient newClient() {
        try {
            // 创建OSSClient实例。
            return (OSSClient) new OSSClientBuilder().build(prop.getEndpoint(), prop.getAccessKeyId(), prop.getAccessKeySecret());
        } catch (OSSException e) {
            log.error("创建OSS Client实例失败", e);
            throw e;
        }
    }

    @PostConstruct
    public void init() {
        log.info("初始化阿里云OSS, arguments={}", JSON.toJSONString(prop));
    }
}
